package unity.messenger.security;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import unity.messenger.security.exceptions.CipherCreationException;
import unity.messenger.security.exceptions.CipherCreationException.Error;
import unity.messenger.security.exceptions.DecryptionException;
import unity.messenger.security.exceptions.EncryptionException;

public class Cipher 
{
	public static enum Transformation
	{
		RSA_ECB_PKCS1Padding ("RSA", "ECB", "PKCS1Padding"),
		RSA_ECB_OAEPPadding ("RSA", "ECB", "OAEPPadding"),
		RSA_ECB_NoPadding ("RSA", "ECB", "NoPadding"),
		ECIES_ECB_PKCS1Padding ("ECIES");
		
		private Transformation(String algorithm, String mode, String paddingScheme)
		{
			this.paddingScheme = paddingScheme;
			this.algorithm = algorithm;
			this.mode = mode;
		}
		
		private Transformation(String algorithm)
		{
			this.algorithm = algorithm;
		}
		
		public String getAlgorithm() 
		{
			return algorithm;
		}

		public String getMode()
		{
			return mode;
		}

		public String getPaddingScheme() 
		{
			return paddingScheme;
		}

		private String algorithm;
		private String mode;
		private String paddingScheme;
	}
	
	private static final boolean ENCRYPT_MODE = true;
	private static final boolean DECRYPT_MODE = false;
	
	private javax.crypto.Cipher cipher;
	
	public Cipher(Transformation transform) throws CipherCreationException
	{
		try
		{
			if(transform.getMode() == null) this.cipher = javax.crypto.Cipher.getInstance(transform.algorithm, new BouncyCastleProvider());
			else this.cipher = javax.crypto.Cipher.getInstance(transform.algorithm + "/" + transform.mode + "/" + transform.paddingScheme, new BouncyCastleProvider());
		}
		catch(NoSuchAlgorithmException e) 
		{
			throw new CipherCreationException("There is no implementation for the algorithm " + transform.algorithm, e).setError(Error.INVALID_ALGORITHM);
		}
		catch(NoSuchPaddingException e) 
		{
			throw new CipherCreationException("There is no implementation for the padding " + transform.paddingScheme, e).setError(Error.INVALID_PADDING_SCHEME);
		}
	} 
	
	public byte[] encrypt(byte[] plaintext, Key key) throws EncryptionException
	{
		try
		{
			return this.execute(ENCRYPT_MODE, plaintext, key);
		}
		catch(Exception e)
		{
			throw new EncryptionException("It was not possible to encrypt the data", e);
		}
	}
	
	public byte[] decrypt(byte[] encryptedtext, Key key) throws DecryptionException
	{
		try
		{
			return this.execute(DECRYPT_MODE, encryptedtext, key);
		}
		catch(Exception e)
		{
			throw new DecryptionException("It was not possible to encrypt the data", e);
		}
	}
	
	private synchronized byte[] execute(boolean mode, byte[] data, Key key) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException
	{
		if(data == null || data.length == 0) return null;
		
		if(mode == ENCRYPT_MODE)
		{
			this.cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key);
		}
		else
		{
			this.cipher.init(javax.crypto.Cipher.DECRYPT_MODE, key);
		}
		
		return this.cipher.doFinal(data);
	}
}
